Android性能优化 (1)—— 内存溢出和内存泄漏的介绍
什么是卡顿
我们可以先说说流畅性: 流畅我们定义为运行程序时达到60fps或以上的绘制效率,且尽可能少丢帧。 流畅和卡顿是建立在不同的标准上。如果强加议论,一定会变成毫无意义的口水战。在这里,卡顿我们定义为程序运行时无法达到60fps,丢帧频繁。
比如说下面的栗子:
堆内存溢出
运行上面代码,不到一分钟将导致手机卡死。我的手机数华为荣耀plus,一会时间手机就卡死。
内存泄露:
指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用。
简单的理解为: 内存没有回收
例如: 没有调用 bitmap.recycle()
内存溢出:
指程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。
简单的理解为: 内存不够使用
例如:申请了一个integer,只有存放integer的空间.但给它存了long才能存下的数,那就是内存溢出。原因:申请不到可用的内存
注意:一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。——>memory leak会最终会导致out of memory!
常见内存泄漏的应用现象:
Android(Java)中常见的容易引起内存泄漏的不良代码
查询数据库没有关闭游标
构造Adapter时,没有使用缓存的 convertView
Bitmap对象不再使用时调用recycle()释放内存
释放对象的引用
使用Adapter时,没有复用convertview
使用非静态内部类
单例模式时需要context参数时,
注册对象时,没有进行反注册
使用资源对象时,没有关闭
使用Bitmap后没有用recycle释放
集合中的对象没有清理
频繁创建对象而没有释放
内存溢出的流程图(来自百度):
java内存泄露的图文介绍:
发生的方式来分类,内存泄漏可以分为4类:
常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。
隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。
大家做项目的时候,我想有些童鞋们不经意间会出现内存泄露的象限:
比如你在onCrate()调用了
ButterKnife.bind()
方法或者
EventBus.regisit()
方法, 但是并没有在相对应的 onDestroy() 方法调用
ButterKnife.unbind()
方法或者
EventBus.unregist()
方法
从用户使用程序的角度来看,内存泄漏本身不会产生什么危害。 作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。
从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到 。
几个内存泄露的案例:
1. 静态集合类 像HashMap、Vector等的使用最容易出现内存泄露
2.当集合里面的对象属性被修改后,再调用remove()方法时不起作用
3、监听器
在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。
4、各种连接
比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。
对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。
5.内部类和外部模块等的引用
例如程序员A 负责A 模块,调用了B 模块的一个方法如:
public void registerMsg(Object b);
这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用
6、单例模式
显然B采用singleton模式,它持有一个A对象的引用,而这个A类的对象将不能被回收。想象下如果A是个比较复杂的对象或者集合类型会发生什么情况
于亚豪的博客地址
如果你觉得此文对您有所帮助,欢迎入群 QQ交流群 :232203809
微信公众号:终端研发部
Hello,伙伴们
长按二维码就可以关注我们啦